
/**
 * twitterapi.cpp
 * 
 * TwitterAPI is a wrapper of Twitter API
 * 
 * Copyright (c) 2008 Harunobu Oyama <nobukichi at gmail.com>
 * Licensed under GNU GPL version 3
 *
 */

#include <qDebug>
#include <QtNetwork>
#include <QtXml> 
#include "settingsdialog.h"
#include "usersettings.h"
#include "twitterapi.h"


inline int engMonthToInt(const QString& strMonth)
{
	//! QDateTime::fromString does not decode English month names, 
	//! when running on non-English locale system
	const char* MONTH_NAME[] = {
					"DMY", "Jan", "Feb", "Mar", "Apr", "May", "Jun", 
						"Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };

	int month = 1;
	for (; month <= 12; ++month) {
		if (strMonth == MONTH_NAME[month]) break;
	}

	return month;
}

inline QDateTime twitterTimeToDateTime(const QString& twitterDateTime)
{
	//! Twitter returns date and time in a format like this:
	//!     "Tue Feb 19 20:56:22 +0000 2008";
	//!       0   1  2     3       4    5      <- index  
	QStringList stringList = twitterDateTime.split(" ");
	if (stringList.size() == 6) {
		QDate date(stringList[5].toInt(), engMonthToInt(stringList[1]), stringList[2].toInt());
		QTime time = QTime::fromString(stringList[3], "hh:mm:ss");
		QDateTime dateTime(date, time, Qt::UTC);
		return dateTime.toLocalTime();

	} else {
		//! oops the given string is in unexpected format
		return QDateTime::currentDateTime();
	}
}


TwitterAPI::TwitterAPI(QObject* parent)
  : QObject(parent), authenticationRequestId(0)
{
	qDebug("QHttp::QHttp");
	setHost("twitter.com");
	
	connect( &http, SIGNAL(requestFinished(int, bool)), 
	         this, SLOT(slotRequestFinished(int, bool)) );
	connect( &http, SIGNAL(requestStarted(int)),
	         this, SLOT(slotEmitSignalRequestStarted()) );
	connect( &http, SIGNAL(done(bool)),
	         this, SLOT(slotEmitSignalRequestDone()) );
}
	
TwitterAPI::~TwitterAPI()
{
	reset();
}

void TwitterAPI::setHost(const QString& host)
{
	int id = http.setHost(host);
	qDebug("QHttp::setHost %d", id);
}

void TwitterAPI::setTwitterCredentials(const QString& userName, const QString& password)
{
	authenticationRequestId = http.setUser(userName, password);
	qDebug("QHttp::setUser %d %s", authenticationRequestId, userName.toStdString().c_str());
}

int TwitterAPI::getMyOwnTimeline(int page)
{
	return sendGetRequest(QString("/statuses/user_timeline.xml?page=%1").arg(page), Request::REQ_TYPE_TWEETS);
}

int TwitterAPI::getMyfriendsTimeline(int page)
{
	return sendGetRequest(QString("/statuses/friends_timeline.xml?page=%1").arg(page), Request::REQ_TYPE_TWEETS);
}

int TwitterAPI::getTimelineOf(const QString& userName, int page)
{
	return sendGetRequest(QString("/statuses/user_timeline/%1.xml?page=%2").arg(userName).arg(page), Request::REQ_TYPE_TWEETS);
}

int TwitterAPI::getFollowingUsersOf(const QString& userName)
{
	return sendGetRequest(QString("/statuses/friends/%1.xml").arg(userName), Request::REQ_TYPE_USER_LIST);
}

int TwitterAPI::sendGetRequest(const QString& path, TwitterAPI::Request::Type reqType)
{
	Request request(reqType);
	int requestId = http.get(path, request.responseBuffer());
	qDebug("QHttp::get %d = %s", requestId, path.toStdString().c_str());
	activeRequests[requestId] = request;
	return requestId;
}

int TwitterAPI::update(const QString& newMessage)
{
	QString path = QString("/statuses/update.xml");

	QByteArray encodedAscii = QUrl::toPercentEncoding(newMessage);
	QString body = "source=nobitter&status=" + QString::fromAscii(encodedAscii.data(), encodedAscii.size());
	
	Request request(Request::REQ_TYPE_UPDATE);
	int requestId = http.post(path, body.toAscii(), request.responseBuffer());
	qDebug("QHttp::post %d", requestId);
	activeRequests[requestId] = request;

	return requestId;
}

void TwitterAPI::slotRequestFinished(int id, bool error)
{
	int statusCode = http.lastResponse().statusCode();

	qDebug("[%d] QHttp::requestFinished %d %s", id, statusCode, (error ? "ERROR" : "OK"));

	if (id == authenticationRequestId) {
		authenticationRequestId = 0;
		emit signalSetCredentialsDone();
		return;
	}

	if (statusCode == 401) {
		if (error) {
			emit signalAuthenticationFailure(id);
		} else {
			getMyfriendsTimeline();
		}
		return;
	}

	if (error) {
		lastHttpError = http.error();
		QTimer::singleShot(0, this, SLOT(slotEmitSignalDetectedHttpError()));
	} else if (statusCode == 200) {
		RequestMap::iterator it = activeRequests.find(id);
		if (it != activeRequests.end()) {
			switch (it->second.type()) {
			case Request::REQ_TYPE_TWEETS:
				emitSignalReceivedTweets(id, *it->second.responseBuffer());
				break;
			case Request::REQ_TYPE_USER_LIST:
				emitSignalReceivedUserList(id, *it->second.responseBuffer());
				break;
			default:
				break;
			}
			activeRequests.erase(id);
		}
	} else if (statusCode == 404) {
		RequestMap::iterator it = activeRequests.find(id);
		if (it != activeRequests.end()) {
			switch (it->second.type()) {
			case Request::REQ_TYPE_TWEETS:
				//! TODO: add erro handling?
				break;
			case Request::REQ_TYPE_USER_LIST:
				emit signalGetFollowingUsersOfFailed(id);
				break;
			default:
				break;
			}
		}
	}
}

inline QString decodeHttpSafeString(const QString& str)
{
	QString ret = str;
	ret.replace("&lt;", "<").replace("&gt;", ">").replace("&star;", "*").replace("&amp;", "&");
	return ret;
}

void TwitterAPI::emitSignalReceivedTweets(int requestId, const QBuffer& buffre)
{
	TweetVector tweets;
	
	QDomDocument dom;
	dom.setContent(buffre.data(), true);
	
	QDomNodeList statusList = dom.elementsByTagName("status");
	for (int i = 0; i < statusList.size(); ++i) {
		QDomElement userElement = statusList.at(i).firstChildElement("user");
		QDomElement nameElement = userElement.firstChildElement("screen_name");
		QDomElement imageElement = userElement.firstChildElement("profile_image_url");
		QDomElement timeElement = statusList.at(i).firstChildElement("created_at");
		QDomElement textElement = statusList.at(i).firstChildElement("text");
		if (!timeElement.isNull() && !nameElement.isNull() && !textElement.isNull()) {
			QDateTime timeStamp = twitterTimeToDateTime(timeElement.text());
			tweets.push_back(Tweet(timeStamp, nameElement.text(), decodeHttpSafeString(textElement.text()), imageElement.text()));
			qDebug() << tweets.back().toString();
		}
	}

	emit signalReceivedTweets(requestId, tweets);
}

void TwitterAPI::emitSignalReceivedUserList(int requestId, const QBuffer& buffre)
{
	QStringList userList;
	
	QDomDocument dom;
	dom.setContent(buffre.data(), true);
	QDomNodeList userElementList = dom.elementsByTagName("user");
	for (int i = 0; i < userElementList.size(); ++i) {
		QDomElement screenNameElement = userElementList.at(i).firstChildElement("screen_name");
		if (!screenNameElement.isNull()) {
			userList << screenNameElement.text();
		}
	}

	emit signalReceivedUserList(requestId, userList);
}

void TwitterAPI::reset()
{
	http.abort();
}

TwitterAPI::Request::Request(TwitterAPI::Request::Type type)
  : reqType(type), ptrResponseBuffer(new QBuffer())
{
}

TwitterAPI::Request::Type TwitterAPI::Request::type() const
{
	return reqType;
}

QBuffer* TwitterAPI::Request::responseBuffer()
{
	return ptrResponseBuffer.get();
}

void TwitterAPI::slotEmitSignalDetectedHttpError()
{
	emit signalDetectedHttpError(lastHttpError);
}

void TwitterAPI::slotEmitSignalRequestStarted()
{
	emit signalRequestStarted();
}

void TwitterAPI::slotEmitSignalRequestDone()
{
	emit signalRequestDone();
}

